// GameEngine.cs
//
// Copyright (c) 2007 Mike Strobel
//
// This source code is subject to the terms of the Microsoft Reciprocal License (Ms-RL).
// For details, see <http://www.opensource.org/licenses/ms-rl.html>.
//
// All other rights reserved.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;

using Supremacy.AI;
using Supremacy.Annotations;
using Supremacy.Buildings;
using Supremacy.Combat;
using Supremacy.Data;
using Supremacy.Diplomacy;
using Supremacy.Economy;
using Supremacy.Entities;
using Supremacy.Orbitals;
using Supremacy.Tech;
using Supremacy.Types;
using Supremacy.Universe;

using Wintellect.PowerCollections;

using Supremacy.Collections;
using System.Threading.Tasks;

namespace Supremacy.Game
{
    /// <summary>
    /// Defines the turn processing phases used by the game engine.
    /// </summary>
    public enum TurnPhase : byte
    {
        WaitOnPlayers = 0,
        PreTurnOperations,
        ResetObjects,
        FleetMovement,
        Combat,
        PopulationGrowth,
        PersonnelTraining,
        Research,
        Scrapping,
        Maintenance,
        ShipProduction,
        Production,
        Trade,
        Morale,
        MapUpdates,
        PostTurnOperations,
        SendUpdates,
        Diplomacy,
        WaitOnAIPlayers
    }

    /// <summary>
    /// Delegate used for event handlers related to changes in the current turn phase.
    /// </summary>
    public delegate void TurnPhaseEventHandler(TurnPhase phase);

    /// <summary>
    /// Delegate used for event handlers related to the initiation of combat.
    /// </summary>
    public delegate void CombatEventHandler(IList<CombatAssets> assets);

    /// <summary>
    /// The turn processing engine used in the game.
    /// </summary>
    public class GameEngine
    {
        /// <summary>
        /// Occurs when the current turn phase has changed.
        /// </summary>
        public event TurnPhaseEventHandler TurnPhaseChanged;

        /// <summary>
        /// Occurs when the current turn phase has finished.
        /// </summary>
        public event TurnPhaseEventHandler TurnPhaseFinished;

        /// <summary>
        /// Occurs when combat is starting.
        /// </summary>
        public event CombatEventHandler CombatOccurring;

        /// <summary>
        /// Occurs when a Fleet moves to a new location.
        /// </summary>
        public event EventHandler<ParameterEventArgs<Fleet>> FleetLocationChanged;

        /// <summary>
        /// Blocks the execution of the turn processing engine while waiting on players
        /// to submit combat orders.
        /// </summary>
        private readonly ManualResetEvent CombatReset = new ManualResetEvent(false);

        /// <summary>
        /// Raises the <see cref="TurnPhaseChanged"/> event.
        /// </summary>
        /// <param name="game">The current game.</param>
        /// <param name="phase">The current turn phase.</param>
        private void OnTurnPhaseChanged(GameContext game, TurnPhase phase)
        {
            if (phase != TurnPhase.SendUpdates)
            {
                foreach (var scriptedEvent in game.ScriptedEvents)
                    scriptedEvent.OnTurnPhaseStarted(game, phase);

                foreach (var agent in game.CivilizationManagers.SelectMany(o => o.AgentPool.CurrentAgents).Cast<IGameTurnListener>())
                    agent.OnTurnPhaseStarted(game, phase);
            }

            var handler = TurnPhaseChanged;
            if (handler != null)
                handler(phase);
        }

        /// <summary>
        /// Raises the <see cref="TurnPhaseChanged"/> event.
        /// </summary>
        /// /// <param name="game">The current game.</param>
        /// <param name="phase">The turn phase that just finished.</param>
        private void OnTurnPhaseFinished(GameContext game, TurnPhase phase)
        {
            if (phase != TurnPhase.SendUpdates)
            {
                foreach (var scriptedEvent in game.ScriptedEvents)
                    scriptedEvent.OnTurnPhaseFinished(game, phase);

                foreach (var agent in game.CivilizationManagers.SelectMany(o => o.AgentPool.CurrentAgents).Cast<IGameTurnListener>())
                    agent.OnTurnPhaseFinished(game, phase);
            }

            var handler = TurnPhaseFinished;
            if (handler != null)
                handler(phase);
        }

        /// <summary>
        /// Raises the <see cref="FleetLocationChanged"/> event.
        /// </summary>
        /// <param name="fleet">A Fleet whose Location just changed.</param>
        private void OnFleetLocationChanged(Fleet fleet)
        {
            var handler = FleetLocationChanged;
            if (handler != null)
                handler(this, new ParameterEventArgs<Fleet>(fleet));
        }

        /// <summary>
        /// Perform turn processing for the specified game context.
        /// </summary>
        /// <param name="game">The game context.</param>
        public void DoTurn([NotNull] GameContext game)
        {
            if (game == null)
                throw new ArgumentNullException("game");

            var start = DateTime.UtcNow;

            HashSet<Fleet> fleets;

            GameContext.PushThreadContext(game);
            try
            {
                var eventsToRemove = game.ScriptedEvents.Where(o => !o.CanExecute).ToList();
                foreach (var eventToRemove in eventsToRemove)
                    game.ScriptedEvents.Remove(eventToRemove);

                foreach (var scriptedEvent in game.ScriptedEvents)
                    scriptedEvent.OnTurnStarted(game);

                foreach (var agent in game.CivilizationManagers.SelectMany(o => o.AgentPool.CurrentAgents).Cast<IGameTurnListener>())
                    agent.OnTurnStarted(game);

                fleets = game.Universe.Find<Fleet>();

                foreach (var fleet in fleets)
                    fleet.LocationChanged += HandleFleetLocationChanged;
            }
            finally { GameContext.PopThreadContext(); }

            OnTurnPhaseChanged(game, TurnPhase.PreTurnOperations);
            GameContext.PushThreadContext(game);
            try { DoPreTurnOperations(game); }
            finally { GameContext.PopThreadContext(); }
            OnTurnPhaseFinished(game, TurnPhase.PreTurnOperations);

            OnTurnPhaseChanged(game, TurnPhase.FleetMovement);
            GameContext.PushThreadContext(game);
            try { DoFleetMovement(game); }
            finally { GameContext.PopThreadContext(); }
            OnTurnPhaseFinished(game, TurnPhase.FleetMovement);

            OnTurnPhaseChanged(game, TurnPhase.Diplomacy);
            GameContext.PushThreadContext(game);
            try { DoDiplomacy(game); }
            finally { GameContext.PopThreadContext(); }
            OnTurnPhaseFinished(game, TurnPhase.Diplomacy);

            OnTurnPhaseChanged(game, TurnPhase.Combat);
            GameContext.PushThreadContext(game);
            try { /*DoCombat(game);*/ }
            finally { GameContext.PopThreadContext(); }
            OnTurnPhaseFinished(game, TurnPhase.Combat);

            OnTurnPhaseChanged(game, TurnPhase.PopulationGrowth);
            GameContext.PushThreadContext(game);
            try { DoPopulation(game); }
            finally { GameContext.PopThreadContext(); }
            OnTurnPhaseFinished(game, TurnPhase.PopulationGrowth);

            OnTurnPhaseChanged(game, TurnPhase.PersonnelTraining);
            GameContext.PushThreadContext(game);
            try { DoPersonnelTraining(game); }
            finally { GameContext.PopThreadContext(); }
            OnTurnPhaseFinished(game, TurnPhase.PersonnelTraining);

            OnTurnPhaseChanged(game, TurnPhase.Research);
            GameContext.PushThreadContext(game);
            try { DoResearch(game); }
            finally { GameContext.PopThreadContext(); }
            OnTurnPhaseFinished(game, TurnPhase.Research);

            OnTurnPhaseChanged(game, TurnPhase.Scrapping);
            GameContext.PushThreadContext(game);
            try { DoScrapping(game); }
            finally { GameContext.PopThreadContext(); }
            OnTurnPhaseFinished(game, TurnPhase.Scrapping);

            OnTurnPhaseChanged(game, TurnPhase.Maintenance);
            GameContext.PushThreadContext(game);
            try { DoMaintenance(game); }
            finally { GameContext.PopThreadContext(); }
            OnTurnPhaseFinished(game, TurnPhase.Maintenance);

            OnTurnPhaseChanged(game, TurnPhase.ShipProduction);
            GameContext.PushThreadContext(game);
            try { DoShipProduction(game); }
            finally { GameContext.PopThreadContext(); }
            OnTurnPhaseFinished(game, TurnPhase.ShipProduction);

            OnTurnPhaseChanged(game, TurnPhase.Production);
            GameContext.PushThreadContext(game);
            try { DoProduction(game); }
            finally { GameContext.PopThreadContext(); }
            OnTurnPhaseFinished(game, TurnPhase.Production);

            OnTurnPhaseChanged(game, TurnPhase.Trade);
            GameContext.PushThreadContext(game);
            try { DoTrade(game); }
            finally { GameContext.PopThreadContext(); }
            OnTurnPhaseFinished(game, TurnPhase.Trade);

            OnTurnPhaseChanged(game, TurnPhase.Morale);
            GameContext.PushThreadContext(game);
            try { DoMorale(game); }
            finally { GameContext.PopThreadContext(); }
            OnTurnPhaseFinished(game, TurnPhase.Morale);

            OnTurnPhaseChanged(game, TurnPhase.MapUpdates);
            GameContext.PushThreadContext(game);
            try { DoMapUpdates(game); }
            finally { GameContext.PopThreadContext(); }
            OnTurnPhaseFinished(game, TurnPhase.MapUpdates);

            OnTurnPhaseChanged(game, TurnPhase.PostTurnOperations);
            GameContext.PushThreadContext(game);
            try { DoPostTurnOperations(game); }
            finally { GameContext.PopThreadContext(); }
            OnTurnPhaseFinished(game, TurnPhase.PostTurnOperations);

            OnTurnPhaseChanged(game, TurnPhase.SendUpdates);

            GameContext.PushThreadContext(game);
            try
            {
                foreach (var scriptedEvent in game.ScriptedEvents)
                    scriptedEvent.OnTurnFinished(game);

                foreach (var agent in game.CivilizationManagers.SelectMany(o => o.AgentPool.CurrentAgents).Cast<IGameTurnListener>())
                    agent.OnTurnFinished(game);
            }
            finally { GameContext.PopThreadContext(); }

            foreach (var fleet in fleets)
                fleet.LocationChanged -= HandleFleetLocationChanged;

            Console.WriteLine("Turn processing time: {0}", DateTime.UtcNow - start);
        }

        private void HandleFleetLocationChanged(object sender, EventArgs e)
        {
            var fleet = sender as Fleet;
            if (fleet != null)
                OnFleetLocationChanged(fleet);
        }

        #region DoPreTurnOperations() Method
        private void DoPreTurnOperations(GameContext game)
        {
            var objects = GameContext.Current.Universe.Objects.ToHashSet();
            var civManagers = GameContext.Current.CivilizationManagers.ToHashSet();
            var fleets = objects.OfType<Fleet>().ToHashSet();
            var errors = new System.Collections.Concurrent.ConcurrentStack<Exception>();

            ParallelForEach(
                objects,
                item =>
                {
                    GameContext.PushThreadContext(game);
                    try
                    {
                        item.Reset();
                    }
                    catch (Exception e)
                    {
                        errors.Push(e);
                    }
                    finally
                    {
                        GameContext.PushThreadContext(game);
                    }
                });

            if (!errors.IsEmpty)
                throw new AggregateException(errors);

            errors.Clear();
            ParallelForEach(
                civManagers,
                civManager =>
                {
                    GameContext.PushThreadContext(game);
                    try
                    {
                        civManager.SitRepEntries.Clear();
                    }
                    catch (Exception e)
                    {
                        errors.Push(e);
                    }
                    finally
                    {
                        GameContext.PopThreadContext();
                    }
                });

            if (!errors.IsEmpty)
                throw new AggregateException(errors);

            // This block is not guaranteed to be safe for parallel execution.
            GameContext.PushThreadContext(game);
            foreach (Fleet fleet in fleets)
            {
                if (fleet.Order != null)
                {
                    fleet.Order.OnTurnBeginning();
                }
            }
        }
        #endregion

        #region DoPreGameSetup() Method
        public void DoPreGameSetup(GameContext game)
        {
            var errors = new System.Collections.Concurrent.ConcurrentStack<Exception>();
            
            ParallelForEach(
                GameContext.Current.Civilizations,
                civ =>
                {
                    GameContext.PushThreadContext(game);
                    try
                    {
                        if (!GameContext.Current.CivilizationManagers.Contains(civ.CivID))
                        {
                            GameContext.Current.CivilizationManagers.Add(new CivilizationManager(game, civ));
                        }
                    }
                    catch (Exception e)
                    {
                        errors.Push(e);
                    }
                    GameContext.PopThreadContext();
                });

            if (!errors.IsEmpty)
                throw new AggregateException(errors);

            GameContext.PushThreadContext(game);
            game.Relationships = RelationshipMap.Create(game);

            GameContext.PushThreadContext(game);
            DoMapUpdates(game);

            game.TurnNumber = 1;
        }
        #endregion

        #region DoFleetMovement() Method
        private void DoFleetMovement(GameContext game)
        {
            var allFleets = GameContext.Current.Universe.Find<Fleet>().ToList();

            foreach (var fleet in allFleets)
            {
                if (fleet.Order == null)
                    continue;

                if (fleet.Order.IsComplete)
                {
                    var completedOrder = fleet.Order;
                    fleet.Order.OnOrderCompleted();
                    /* 
                         * It is possible that invoking OnCompletedOrder() on the completed
                         * order caused a new (or prior) order to be issued.  In this case,
                         * we let that order stand.  Otherwise, we revert to the default order.
                         */
                    if (fleet.Order == completedOrder)
                        fleet.SetOrder(fleet.GetDefaultOrder());
                }
                else if (!fleet.Order.IsValidOrder(fleet))
                {
                    var cancelledOrder = fleet.Order;
                    fleet.Order.OnOrderCancelled();
                    if (fleet.Order != cancelledOrder)
                        fleet.SetOrder(fleet.GetDefaultOrder());
                }
            }

            foreach (var fleet in allFleets)
            {
                var civManager = GameContext.Current.CivilizationManagers[fleet.Owner];
                int fuelNeeded;
                int fuelRange = civManager.MapData.GetFuelRange(fleet.Location);
                
                /*
                 * If the fleet is within fueling range, then try to top off the reserves of
                 * each ship in the fleet.  We do this now in case a ship is out of fuel, but
                 * is now within fueling range, thus ensuring the ship will be able to move.
                 */
                if (!fleet.IsInTow && (fleet.Range >= fuelRange))
                {
                    foreach (var ship in fleet.Ships)
                    {
                        fuelNeeded = ship.FuelReserve.Maximum - ship.FuelReserve.CurrentValue;
                        ship.FuelReserve.AdjustCurrent(
                            civManager.Resources[ResourceType.Deuterium].AdjustCurrent(-fuelNeeded));
                    }
                }

                for (int i = 0; i < fleet.Speed; i++)
                {
                    if (fleet.MoveAlongRoute())
                    {
                        foreach (Ship ship in fleet.Ships)
                            ship.ExperienceLevel += 5;
                    }
                    else
                    {
                        if (i == 0)
                        {
                            foreach (Ship ship in fleet.Ships)
                                ship.ExperienceLevel++;
                        }
                        break;
                    }
                    fuelRange = civManager.MapData.GetFuelRange(fleet.Location);
                    foreach (var ship in fleet.Ships)
                    {
                        /*
                         * For each ship in the fleet, deplete the fuel reserves by a 1 unit
                         * of Deuterium.  Then, if the fleet is within fueling range, attempt
                         * to replenish that unit from the global stockpile.
                         */
                        fuelNeeded = ship.FuelReserve.AdjustCurrent(-1);
                        if (fleet.Range >= fuelRange)
                        {
                            ship.FuelReserve.AdjustCurrent(
                                civManager.Resources[ResourceType.Deuterium].AdjustCurrent(-fuelNeeded));
                        }
                        //ship.FuelReserve.UpdateAndReset();
                    }

                    if (!fleet.Order.IsComplete)
                        continue;

                    var completedOrder = fleet.Order;
                    fleet.Order.OnOrderCompleted();

                    /* 
                     * It is possible that invoking OnCompletedOrder() caused a new (or prior)
                     * order to be issued.  In this case, we let that order stand.  Otherwise,
                     * we revert to the default order.
                     */
                    if (fleet.Order == completedOrder)
                        fleet.SetOrder(fleet.GetDefaultOrder());
                }

                if (fleet.IsStranded && !FleetHelper.IsFleetInFuelRange(fleet))
                {
                    if (fleet.IsRouteLocked)
                        fleet.UnlockRoute();
                    fleet.SetRoute(TravelRoute.Empty);
                }

                if (fleet.Order.IsComplete)
                {
                    var completedOrder = fleet.Order;
                    fleet.Order.OnOrderCompleted();
                    
                    /* 
                     * It is possible that invoking OnCompletedOrder() caused a new (or prior)
                     * order to be issued.  In this case, we let that order stand.  Otherwise,
                     * we revert to the default order.
                     */
                    if (fleet.Order == completedOrder)
                        fleet.SetOrder(fleet.GetDefaultOrder());
                }
                else if (!fleet.Order.IsValidOrder(fleet))
                {
                    fleet.SetOrder(fleet.GetDefaultOrder());
                }
            }
        }
        #endregion

        #region DoDiplomacy() Method
        private void DoDiplomacy(GameContext game)
        {
            HashSet<Relationship> processedRelationships = new HashSet<Relationship>();
            //// Handle diplomatic messsages
            //foreach (Civilization civ1 in GameContext.Current.Civilizations)
            //{
            //    foreach (Civilization civ2 in GameContext.Current.Civilizations)
            //    {   
            //        if (civ1 != civ2)
            //        {
            //            Relationship relationship1 = GameContext.Current.Relationships[civ1, civ2];
            //            Relationship relationship2 = GameContext.Current.Relationships[civ2, civ1];
            //            Debug.Assert(relationship1 != null);
            //            Debug.Assert(relationship2 != null);
            //            if (processedRelationships.Contains(relationship1) || processedRelationships.Contains(relationship2))
            //                continue;
            //            processedRelationships.Add(relationship1);
            //            processedRelationships.Add(relationship2);
            //            /* Handle war declarations first, as they will cause proposals from the other side
            //               to be rescinded. */
            //            if (relationship2.HasOutgoingMessage && relationship2.Outbox.IsWarDeclaration)
            //            {
            //                // TODO: Handle retracted messages
            //                relationship1.Inbox = relationship2.Outbox;
            //                relationship2.Outbox = null;
            //                relationship1.OnTurnElapsed();
            //                relationship2.OnTurnElapsed();
            //            }
            //            else if (relationship1.HasOutgoingMessage && relationship1.Outbox.IsWarDeclaration)
            //            {
            //                // TODO: Handle retracted messages
            //                relationship2.Inbox = relationship1.Outbox;
            //                relationship1.Outbox = null;
            //                relationship2.OnTurnElapsed();
            //                relationship1.OnTurnElapsed();
            //            }
            //            if (relationship1.Inbox != null)
            //            {
            //                GameContext.Current.CivilizationManagers[relationship1.OwnerCiv].SitRepEntries.Add(
            //                    new DiplomaticMessageSitRepEntry(
            //                        relationship1.OwnerCiv,
            //                        relationship1.Inbox));
            //                GameContext.Current.CivilizationManagers[relationship1.OtherCiv].SitRepEntries.Add(
            //                    new DiplomaticMessageSitRepEntry(
            //                        relationship1.OtherCiv,
            //                        relationship1.Inbox));
            //            }
            //        }
            //    }
            //}
            
            //processedRelationships.Clear();
            
            //// Run turn processing with messages from last turn.
            //foreach (Civilization civ1 in GameContext.Current.Civilizations)
            //{
            //    foreach (Civilization civ2 in GameContext.Current.Civilizations)
            //    {
            //        if (civ1 != civ2)
            //        {
            //            Relationship relationship1 = GameContext.Current.Relationships[civ1, civ2];
            //            Relationship relationship2 = GameContext.Current.Relationships[civ2, civ1];
            //            Debug.Assert(relationship1 != null);
            //            Debug.Assert(relationship2 != null);
            //            if (processedRelationships.Contains(relationship1) || processedRelationships.Contains(relationship2))
            //                continue;
            //            processedRelationships.Add(relationship1);
            //            processedRelationships.Add(relationship2);
            //            if (!relationship1.AtWar || (relationship1.TurnsSinceStatusChange > 0))
            //            {
            //                relationship1.ReplyInbox = relationship2.ReplyOutbox;
            //                relationship2.ReplyOutbox = null;
            //                relationship1.OnTurnElapsed();
            //                if (relationship2.ReplyInbox != null)
            //                {
            //                    GameContext.Current.CivilizationManagers[relationship2.OwnerCiv].SitRepEntries.Add(
            //                        new DiplomaticMessageSitRepEntry(
            //                            relationship2.OwnerCiv,
            //                            relationship2.ReplyInbox));
            //                    GameContext.Current.CivilizationManagers[relationship2.OtherCiv].SitRepEntries.Add(
            //                        new DiplomaticMessageSitRepEntry(
            //                            relationship2.OtherCiv,
            //                            relationship2.ReplyInbox));
            //                }
            //            }
            //            if (!relationship2.AtWar || (relationship2.TurnsSinceStatusChange > 0))
            //            {
            //                relationship2.ReplyInbox = relationship1.ReplyOutbox;
            //                relationship1.ReplyOutbox = null;
            //                relationship2.OnTurnElapsed();
            //                if (relationship1.ReplyInbox != null)
            //                {
            //                    GameContext.Current.CivilizationManagers[relationship1.OwnerCiv].SitRepEntries.Add(
            //                        new DiplomaticMessageSitRepEntry(
            //                            relationship1.OwnerCiv,
            //                            relationship1.ReplyInbox));
            //                    GameContext.Current.CivilizationManagers[relationship1.OtherCiv].SitRepEntries.Add(
            //                        new DiplomaticMessageSitRepEntry(
            //                            relationship1.OtherCiv,
            //                            relationship1.ReplyInbox));
            //                }
            //            }
            //        }
            //    }
            //}

            //processedRelationships.Clear();

            //// Move all new messages from the sender's outbox to the recipient's inbox
            //foreach (Civilization civ1 in GameContext.Current.Civilizations)
            //{
            //    foreach (Civilization civ2 in GameContext.Current.Civilizations)
            //    {
            //        if (civ1 != civ2)
            //        {
            //            Relationship relationship1 = GameContext.Current.Relationships[civ1, civ2];
            //            Relationship relationship2 = GameContext.Current.Relationships[civ2, civ1];
            //            Debug.Assert(relationship1 != null);
            //            Debug.Assert(relationship2 != null);
            //            if (processedRelationships.Contains(relationship1) || processedRelationships.Contains(relationship2))
            //                continue;
            //            processedRelationships.Add(relationship1);
            //            processedRelationships.Add(relationship2);
            //            if ((relationship1.AtWar && (relationship1.TurnsSinceStatusChange == 0))
            //                || (relationship2.AtWar && (relationship2.TurnsSinceStatusChange == 0)))
            //            {
            //                continue;
            //            }
            //            relationship1.Inbox = relationship2.Outbox;
            //            relationship2.Outbox = null;
            //            if (relationship1.Inbox != null)
            //            {
            //                GameContext.Current.CivilizationManagers[relationship1.OwnerCiv].SitRepEntries.Add(
            //                    new DiplomaticMessageSitRepEntry(
            //                        relationship1.OwnerCiv,
            //                        relationship1.Inbox));
            //                GameContext.Current.CivilizationManagers[relationship1.OtherCiv].SitRepEntries.Add(
            //                    new DiplomaticMessageSitRepEntry(
            //                        relationship1.OtherCiv,
            //                        relationship1.Inbox));
            //            }
            //            if (relationship1.ReplyInbox != null)
            //            {
            //                GameContext.Current.CivilizationManagers[relationship1.OwnerCiv].SitRepEntries.Add(
            //                    new DiplomaticMessageSitRepEntry(
            //                        relationship1.OwnerCiv,
            //                        relationship1.ReplyInbox));
            //                GameContext.Current.CivilizationManagers[relationship1.OtherCiv].SitRepEntries.Add(
            //                    new DiplomaticMessageSitRepEntry(
            //                        relationship1.OtherCiv,
            //                        relationship1.ReplyInbox));
            //            }
            //            relationship2.Inbox = relationship1.Outbox;
            //            relationship1.Outbox = null;
            //            if (relationship2.Inbox != null)
            //            {
            //                GameContext.Current.CivilizationManagers[relationship2.OwnerCiv].SitRepEntries.Add(
            //                    new DiplomaticMessageSitRepEntry(
            //                        relationship2.OwnerCiv,
            //                        relationship2.Inbox));
            //                GameContext.Current.CivilizationManagers[relationship2.OtherCiv].SitRepEntries.Add(
            //                    new DiplomaticMessageSitRepEntry(
            //                        relationship2.OtherCiv,
            //                        relationship2.Inbox));
            //            }
            //            if (relationship2.ReplyInbox != null)
            //            {
            //                GameContext.Current.CivilizationManagers[relationship2.OwnerCiv].SitRepEntries.Add(
            //                    new DiplomaticMessageSitRepEntry(
            //                        relationship2.OwnerCiv,
            //                        relationship2.ReplyInbox));
            //                GameContext.Current.CivilizationManagers[relationship2.OtherCiv].SitRepEntries.Add(
            //                    new DiplomaticMessageSitRepEntry(
            //                        relationship2.OtherCiv,
            //                        relationship2.ReplyInbox));
            //            }
            //        }
            //    }
            //}

            // Move all new messages from the sender's outbox to the recipient's inbox
            foreach (var civ1 in GameContext.Current.Civilizations)
            {
                foreach (var civ2 in GameContext.Current.Civilizations)
                {
                    if (civ1 == civ2)
                        continue;

                    var relationship1 = GameContext.Current.Relationships[civ1, civ2];
                    var relationship2 = GameContext.Current.Relationships[civ2, civ1];
                    Debug.Assert(relationship1 != null);
                    Debug.Assert(relationship2 != null);
                    if (processedRelationships.Contains(relationship1) || processedRelationships.Contains(relationship2))
                        continue;
                    processedRelationships.Add(relationship1);
                    processedRelationships.Add(relationship2);
                    if (relationship1.HasOutgoingMessage && relationship1.Outbox.IsWarDeclaration)
                    {
                        // TODO: Handle retracted messages
                        relationship2.Inbox = relationship1.Outbox;
                        relationship1.Outbox = null;
                        if (relationship2.Inbox != null)
                        {
                            GameContext.Current.CivilizationManagers[relationship2.OwnerCiv].SitRepEntries.Add(
                                new DiplomaticMessageSitRepEntry(
                                    relationship2.OwnerCiv,
                                    relationship2.Inbox));
                            GameContext.Current.CivilizationManagers[relationship2.OtherCiv].SitRepEntries.Add(
                                new DiplomaticMessageSitRepEntry(
                                    relationship2.OtherCiv,
                                    relationship2.Inbox));
                        }
                    }
                    else if (relationship2.HasOutgoingMessage && relationship2.Outbox.IsWarDeclaration)
                    {
                        // TODO: Handle retracted messages
                        relationship1.Inbox = relationship2.Outbox;
                        relationship2.Outbox = null;
                        if (relationship1.Inbox != null)
                        {
                            GameContext.Current.CivilizationManagers[relationship1.OwnerCiv].SitRepEntries.Add(
                                new DiplomaticMessageSitRepEntry(
                                    relationship1.OwnerCiv,
                                    relationship1.Inbox));
                            GameContext.Current.CivilizationManagers[relationship1.OtherCiv].SitRepEntries.Add(
                                new DiplomaticMessageSitRepEntry(
                                    relationship1.OtherCiv,
                                    relationship1.Inbox));
                        }
                    }
                    relationship1.OnTurnElapsed();
                    relationship2.OnTurnElapsed();
                    relationship1.ReplyInbox = relationship2.ReplyOutbox;
                    relationship2.ReplyOutbox = null;
                    relationship2.ReplyInbox = relationship1.ReplyOutbox;
                    relationship1.ReplyOutbox = null;
                    relationship1.Inbox = relationship2.Outbox;
                    relationship2.Outbox = null;
                    relationship2.Inbox = relationship1.Outbox;
                    relationship1.Outbox = null;
                    if (relationship1.ReplyInbox != null)
                    {
                        GameContext.Current.CivilizationManagers[relationship1.OwnerCiv].SitRepEntries.Add(
                            new DiplomaticMessageSitRepEntry(
                                relationship1.OwnerCiv,
                                relationship1.ReplyInbox));
                        GameContext.Current.CivilizationManagers[relationship1.OtherCiv].SitRepEntries.Add(
                            new DiplomaticMessageSitRepEntry(
                                relationship1.OtherCiv,
                                relationship1.ReplyInbox));
                    }
                    if (relationship1.Inbox != null)
                    {
                        GameContext.Current.CivilizationManagers[relationship1.OwnerCiv].SitRepEntries.Add(
                            new DiplomaticMessageSitRepEntry(
                                relationship1.OwnerCiv,
                                relationship1.Inbox));
                        GameContext.Current.CivilizationManagers[relationship1.OtherCiv].SitRepEntries.Add(
                            new DiplomaticMessageSitRepEntry(
                                relationship1.OtherCiv,
                                relationship1.Inbox));
                    }
                    if (relationship2.ReplyInbox != null)
                    {
                        GameContext.Current.CivilizationManagers[relationship2.OwnerCiv].SitRepEntries.Add(
                            new DiplomaticMessageSitRepEntry(
                                relationship2.OwnerCiv,
                                relationship2.ReplyInbox));
                        GameContext.Current.CivilizationManagers[relationship2.OtherCiv].SitRepEntries.Add(
                            new DiplomaticMessageSitRepEntry(
                                relationship2.OtherCiv,
                                relationship2.ReplyInbox));
                    }
                    if (relationship2.Inbox != null)
                    {
                        GameContext.Current.CivilizationManagers[relationship2.OwnerCiv].SitRepEntries.Add(
                            new DiplomaticMessageSitRepEntry(
                                relationship2.OwnerCiv,
                                relationship2.Inbox));
                        GameContext.Current.CivilizationManagers[relationship2.OtherCiv].SitRepEntries.Add(
                            new DiplomaticMessageSitRepEntry(
                                relationship2.OtherCiv,
                                relationship2.Inbox));
                    }
                }
            }
        }
        #endregion

        #region DoCombat() Method
        void DoCombat(GameContext game)
        {
            var locations = new HashSet<MapLocation>();
            var combats = new List<IList<CombatAssets>>();
            foreach (var fleet in GameContext.Current.Universe.Find<Fleet>())
            {
                if (locations.Contains(fleet.Location))
                    continue;
                var assets = CombatHelper.GetCombatAssets(fleet.Location);
                if (assets.Count > 1)
                    combats.Add(assets);
                locations.Add(fleet.Location);
            }
            foreach (var combat in combats)
            {
                CombatReset.Reset();
                OnCombatOccurring(combat);
                CombatReset.WaitOne();
            }
        }
        #endregion

        #region DoPopulation() Method
        void DoPopulation(GameContext game)
        {
            var errors = new System.Collections.Concurrent.ConcurrentStack<Exception>();

            ParallelForEach(
                GameContext.Current.Civilizations,
                civ =>
                {
                    GameContext.PushThreadContext(game);
                    try
                    {
                        CivilizationManager civManager = GameContext.Current.CivilizationManagers[civ.CivID];
                        civManager.TotalPopulation.Reset();

                        foreach (Colony colony in civManager.Colonies)
                        {
                            int popChange = 0;
                            int foodDeficit;

                            colony.FoodReserves.AdjustCurrent(colony.GetProductionOutput(ProductionCategory.Food));
                            foodDeficit = Math.Min(colony.FoodReserves.CurrentValue - colony.Population.CurrentValue, 0);
                            colony.FoodReserves.AdjustCurrent(-1 * colony.Population.CurrentValue);
                            colony.FoodReserves.UpdateAndReset();

                            /*
                             * If there is not enough food to feed the population, we need to kill off some of the
                             * population due to starvation.  Otherwise, we increase the population according to the
                             * growth rate iff we did not suffer a loss due to starvation during the previous turn.
                             * We want to ensure that there is a 1-turn period between population loss and recovery.
                             */
                            if (foodDeficit < 0)
                            {
                                popChange = -(int)Math.Floor(
                                                      0.1 * Math.Sqrt(
                                                                Math.Abs(colony.Population.CurrentValue * foodDeficit)));
                                civManager.SitRepEntries.Add(new StarvationSitRepEntry(civ, colony));
                            }
                            else if (colony.Population.LastChange >= 0)
                            {
                                var growthRate = colony.System.GetGrowthRate(colony.Inhabitants);
                                foreach (var building in colony.Buildings)
                                {
                                    if (!building.IsActive)
                                        continue;
                                    foreach (var buildingBonus in building.BuildingDesign.Bonuses)
                                    {
                                        if (buildingBonus.BonusType == BonusType.PercentGrowthRate)
                                        {
                                            growthRate *= (0.01f * buildingBonus.Amount);
                                        }
                                    }
                                }
                                popChange = (int)Math.Ceiling(growthRate * colony.Population.CurrentValue);
                            }

                            int newPopulation = colony.Population.AdjustCurrent(popChange);
                            colony.Population.UpdateAndReset();
                            civManager.TotalPopulation.AdjustCurrent(colony.Population.CurrentValue);

                            if (newPopulation < colony.Population.Maximum)
                            {
                                var foodFacilityType = colony.GetFacilityType(ProductionCategory.Food);
                                if ((foodFacilityType != null)
                                    && (colony.GetAvailableLabor() >= foodFacilityType.LaborCost))
                                {
                                    int popInThreeTurns = Math.Min(
                                        colony.Population.Maximum,
                                        (int)
                                        (newPopulation * (1 + colony.GrowthRate) * (1 + colony.GrowthRate)
                                         * (1 + colony.GrowthRate)));
                                    while (popInThreeTurns > colony.GetProductionOutput(ProductionCategory.Food))
                                    {
                                        if (!colony.ActivateFacility(ProductionCategory.Food))
                                            break;
                                    }
                                }
                            }

                            while (colony.ActivateFacility(ProductionCategory.Industry))
                                continue;
                        }
                    }
                    catch (Exception e)
                    {
                        errors.Push(e);
                    }
                    finally
                    {
                        GameContext.PopThreadContext();
                    }
                });

            if (!errors.IsEmpty)
                throw new AggregateException(errors);
        }
        #endregion

        #region DoPersonnelTraining Method

        private void DoPersonnelTraining(GameContext game)
        {
            ParallelForEach(
                game.CivilizationManagers.Where(o => o.Civilization.IsEmpire),
                civManager =>
                {
                    GameContext.PushThreadContext(game);

                    try
                    {
                        civManager.AgentPool.Update();
                    }
                    finally
                    {
                        GameContext.PopThreadContext();
                    }
                });
        }

        #endregion

        #region DoResearch() Method
        private void DoResearch(GameContext game)
        {
            var errors = new System.Collections.Concurrent.ConcurrentStack<Exception>();

            ParallelForEach(
                GameContext.Current.Civilizations,
                civ =>
                {
                    GameContext.PushThreadContext(game);
                    try
                    {
                        var civManager = GameContext.Current.CivilizationManagers[civ.CivID];
                        var researchPool = civManager.Research;
                        int researchPoints = 0;
                        
                        var researchBonus = civManager.GlobalBonuses
                            .Where(o => o.BonusType == BonusType.PercentResearchEmpireWide)
                            .Sum(o => 0.01f * o.Amount);
                        
                        foreach (var colony in civManager.Colonies)
                            researchPoints += colony.GetProductionOutput(ProductionCategory.Research);

                        researchPoints += (int)(researchBonus * researchPoints);
                        researchPool.UpdateResearch(researchPoints);
                    }
                    catch (Exception e)
                    {
                        errors.Push(e);
                    }
                    finally
                    {
                        GameContext.PopThreadContext();
                    }
                });

            if (!errors.IsEmpty)
                throw new AggregateException(errors);
        }
        #endregion

        #region DoMapUpdates() Method
        private void DoMapUpdates(GameContext game)
        {
            DoSectorClaims(game);

            GameContext.PushThreadContext(game);

            var map = game.Universe.Map;
            var errors = new System.Collections.Concurrent.ConcurrentStack<Exception>();
            
            var interference = new Task<int[,]>(
                () =>
                {
                    var array = new int[map.Width,map.Height];

                    GameContext.PushThreadContext(game);
                    try
                    {
                        foreach (StarSystem starSystem in game.Universe.Find(UniverseObjectType.StarSystem))
                            StarHelper.ApplySensorInterference(array, starSystem);
                    }
                    finally
                    {
                        GameContext.PopThreadContext();
                    }

                    return array;
                });

            interference.Start();
            
            ParallelForEach(
                game.Civilizations,
                //new ParallelOptions { MaxDegreeOfParallelism = 4 },
                civ => {
                    GameContext.PushThreadContext(game);
                    try
                    {
                        var fuelLocations = new HashSet<MapLocation>();
                        var civManager = game.CivilizationManagers[civ];
                        var mapData = civManager.MapData;

                        mapData.ResetScanStrengthAndFuelRange();

                        foreach (var fleet in game.Universe.FindOwned<Fleet>(civ))
                        {
                            mapData.UpgradeScanStrength(
                                fleet.Location,
                                fleet.ScanStrength,
                                fleet.SensorRange,
                                0,
                                1);
                        }

                        foreach (var station in game.Universe.FindOwned<Station>(civ))
                        {
                            mapData.UpgradeScanStrength(
                                station.Location,
                                station.StationDesign.ScanStrength,
                                station.StationDesign.SensorRange,
                                0,
                                1);

                            fuelLocations.Add(station.Location);
                        }

                        foreach (var colony in civManager.Colonies)
                        {
                            int scanModifier = 0;
                            
                            var scanBonuses = colony.Buildings
                                .Where(o => o.IsActive)
                                .SelectMany(o => o.BuildingDesign.Bonuses)
                                .Where(o => o.BonusType == BonusType.ScanRange)
                                .Select(o => o.Amount);

                            if (scanBonuses.Any())
                                scanModifier = scanBonuses.Max();

                            mapData.UpgradeScanStrength(
                                colony.Location,
                                1 + scanModifier,
                                1 + scanModifier,
                                0,
                                1);
                        }

                        for (var x = 0; x < map.Width; x++)
                        {
                            for (var y = 0; y < map.Height; y++)
                            {
                                var sector = map[x, y];

                                foreach (var fuelLocation in fuelLocations)
                                {    
                                    mapData.UpgradeFuelRange(
                                        sector.Location,
                                        MapLocation.GetDistance(fuelLocation, sector.Location));
                                }
                            }
                        }

                        mapData.ApplyScanInterference(interference.Result);
                    }
                    catch(Exception e)
                    {
                        errors.Push(e);
                    }
                    finally
                    {
                        GameContext.PopThreadContext();
                    }
                });

            if (!errors.IsEmpty)
                throw new AggregateException(errors);
        }

        private void DoSectorClaims(GameContext game)
        {
            var errors = new System.Collections.Concurrent.ConcurrentStack<Exception>();
            var map = game.Universe.Map;
            var sectorClaims = game.SectorClaims;

            sectorClaims.ClearClaims();

            ParallelForEach(
                GameContext.Current.Civilizations.Where(o => o.IsEmpire),
                civ => {
                    GameContext.PushThreadContext(game);
                    try
                    {
                        var civManager = GameContext.Current.CivilizationManagers[civ];

                        foreach (var colony in civManager.Colonies)
                        {
                            var minX = colony.Location.X;
                            var minY = colony.Location.Y;
                            var maxX = colony.Location.X;
                            var maxY = colony.Location.Y;
                            var radius = Math.Min(colony.Population.CurrentValue / 100, 3);

                            minX = Math.Max(0, minX - radius);
                            minY = Math.Max(0, minY - radius);
                            maxX = Math.Min(map.Width - 1, maxX + radius);
                            maxY = Math.Min(map.Height - 1, maxY + radius);

                            for (var x = minX; x <= maxX; x++)
                            {
                                for (var y = minY; y <= maxY; y++)
                                {
                                    var location = new MapLocation(x, y);

                                    var claimWeight = colony.Population.CurrentValue /
                                                      (MapLocation.GetDistance(location, colony.Location) + 1);

                                    if (claimWeight <= 0)
                                        continue;

                                    lock (sectorClaims)
                                        sectorClaims.AddClaim(location, civ, claimWeight);

                                    civManager.MapData.SetScanned(location, true);
                                }
                            }
                        }

                        if (civ.IsHuman)
                            civManager.DesiredBorders = new ConvexHullSet(Enumerable.Empty<ConvexHull>()); //PlayerAI.CreateDesiredBorders(civ);

                    }
                    catch (Exception e)
                    {
                        errors.Push(e);
                    }
                    finally
                    {
                        GameContext.PopThreadContext();
                    }
                });

            if (!errors.IsEmpty)
                throw new AggregateException(errors);
        }
        #endregion

        #region DoScrapping() Method
        void DoScrapping(GameContext game)
        {
            var priorThreadContext = GameContext.ThreadContext;
            GameContext.PushThreadContext(game);
            try
            {
                foreach (var scrappedObject in game.Universe.Find<TechObject>().Where(o => o.Scrap))
                    game.Universe.Scrap(scrappedObject);

                var colonies = game.Civilizations
                    .Select(o => game.CivilizationManagers[o.CivID])
                    .SelectMany(o => o.Colonies);

                foreach (var colony in colonies)
                    game.Universe.ScrapNonStructures(colony);
            }
            finally
            {
                GameContext.PopThreadContext();
            }
        }
        #endregion

        #region DoMaintenance() Method
        private void DoMaintenance(GameContext game)
        {
            foreach (Civilization civ in GameContext.Current.Civilizations)
            {
                CivilizationManager civManager = GameContext.Current.CivilizationManagers[civ];
                foreach (Colony colony in civManager.Colonies)
                {
                    colony.EnsureEnergyForBuildings();
                }
                foreach (TechObject item in GameContext.Current.Universe.FindOwned<TechObject>(civ))
                {
                    civManager.Credits.AdjustCurrent(-item.Design.MaintenanceCost);
                }
            }
        }
        #endregion

        #region DoProduction() Method
        void DoProduction(GameContext game)
        {
            var errors = new System.Collections.Concurrent.ConcurrentStack<Exception>();
            /*
             * Break down production by civilization.  We want to use resources
             * from both the colonies and the global reserves, so this is the
             * sensible way to do it.
             */
            ParallelForEach(
                GameContext.Current.Civilizations,
                civ =>
                {
                    GameContext.PushThreadContext(game);
                    try
                    {
                        CivilizationManager civManager = GameContext.Current.CivilizationManagers[civ.CivID];

                        /* Iterate through each colony */
                        foreach (Colony colony in civManager.Colonies)
                        {
                            /* We want to capture the industry output available at the colony. */
                            int industry = colony.NetIndustry;
                            bool projectFinished = false;

                            /* We know that colonies only have one build slot. */
                            var slot = colony.BuildSlots[0];
                            var queue = colony.BuildQueue;
                            BuildProject lastProject = null;

                            if (!colony.IsProductionAutomated)
                            {
                                colony.ClearBuildPrioritiesAndConsolidate();
                            }

                            for (int i = 0; i < queue.Count; i++)
                            {
                                var buildingDesign = queue[i].Project.BuildDesign as BuildingDesign;
                                if ((buildingDesign != null) && TechTreeHelper.IsBuildLimitReached(civ, buildingDesign))
                                    queue.RemoveAt(i--);
                            }

                            if (slot.Project != null)
                            {
                                var buildingDesign = slot.Project.BuildDesign as BuildingDesign;
                                if ((buildingDesign != null) && TechTreeHelper.IsBuildLimitReached(civ, buildingDesign))
                                {
                                    slot.Project.Cancel();
                                    slot.Project = null;
                                }
                                else if (slot.Project.IsPaused)
                                {
                                    continue;
                                }
                                else if (slot.Project.IsCompleted)
                                {
                                    slot.Project.Finish();
                                    slot.Project = null;
                                    projectFinished = true;
                                }
                            }
                            
                            if (slot.Project == null)
                            {
                                if (queue.Count > 0)
                                    colony.ProcessQueue();
                            }

                        BuildNextProject:
                            if ((slot.Project != null) && (industry > 0))
                            {
                                var totalRes = new ResourceValueCollection();
                                totalRes[ResourceType.Dilithium] = civManager.Resources.Dilithium.CurrentValue;
                                totalRes[ResourceType.Deuterium] = civManager.Resources.Deuterium.CurrentValue;
                                totalRes[ResourceType.RawMaterials] = civManager.Resources.RawMaterials.CurrentValue;
                                //totalRes[ResourceType.Personnel] = civManager.Personnel.Officers.CurrentValue;

                                var totalResClone = totalRes.Clone();

                                /* 
                                 * Advance the production project.  This will automatically
                                 * deduct the necessary (optimal) amounts from the 'industry'
                                 * and 'totalRes' parameters.
                                 */
                                slot.Project.Advance(ref industry, totalRes);
                                lastProject = slot.Project;

                                /* 
                                 * If the project was finished, call Finish() to produce the
                                 * results, then deqeue next project.
                                 */
                                if (slot.Project.IsCompleted)
                                {
                                    slot.Project.Finish();
                                    slot.Project = null;
                                    projectFinished = true;
                                    if (queue.Count > 0)
                                    {
                                        colony.ProcessQueue();
                                    }
                                }

                                /* Check to see if we tapped into the global resource stockpiles */
                                int dDilithium = totalResClone[ResourceType.Dilithium]
                                                 - totalRes[ResourceType.Dilithium];
                                int dDeuterium = totalResClone[ResourceType.Deuterium]
                                                 - totalRes[ResourceType.Deuterium];
                                int dRawMaterials = totalResClone[ResourceType.RawMaterials]
                                                    - totalRes[ResourceType.RawMaterials];
                                //int dPersonnel = totalResClone[ResourceType.Personnel]
                                //                 - totalRes[ResourceType.Personnel];

                                if (dDilithium > 0)
                                {
                                    civManager.Resources.Dilithium.AdjustCurrent(-dDilithium);
                                }

                                if (dDeuterium > 0)
                                {
                                    civManager.Resources.Deuterium.AdjustCurrent(-dDeuterium);
                                }

                                if (dRawMaterials > 0)
                                {
                                    civManager.Resources.RawMaterials.AdjustCurrent(-dRawMaterials);
                                }

/*
                                if (dPersonnel > 0)
                                {
                                    civManager.Personnel[PersonnelCategory.Officers].AdjustCurrent(-dPersonnel);
                                }
*/
                            }

                            /* 
                             * If we haven't expended all of our available industry, run
                             * through the production cycle again.  This should only happen
                             * if the current build project was just completed during the
                             * last pass.
                             */
                            if ((slot.Project != null) && (slot.Project != lastProject) && (industry > 0))
                                goto BuildNextProject;

                            if (projectFinished && (slot.Project == null))
                                civManager.SitRepEntries.Add(new BuildQueueEmptySitRepEntry(civ, colony, false));

                            /*
                             * Update the civilization's treasury to include any credits that
                             * were generated by this colony.
                             */
                            if (colony.TaxCredits > 0)
                            {
                                civManager.Credits.AdjustCurrent(colony.TaxCredits);
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        errors.Push(e);
                    }
                    finally
                    {
                        GameContext.PopThreadContext();
                    }
                });

            if (!errors.IsEmpty)
                throw new AggregateException(errors);
        }
        #endregion

        #region DoShipProduction() Method
        private void DoShipProduction(GameContext game)
        {
            var errors = new System.Collections.Concurrent.ConcurrentStack<Exception>();
            /*
             * Break down production by civilization.  We want to use resources
             * from both the colonies and the global reserves, so this is the
             * sensible way to do it.
             */
            ParallelForEach(
                GameContext.Current.Civilizations,
                civ =>
                {
                    GameContext.PushThreadContext(game);
                    try
                    {
                        var civManager = GameContext.Current.CivilizationManagers[civ.CivID];
                        var colonies = new List<Colony>(civManager.Colonies);

                        /* 
                         * Shuffle the colonies so they are processed in random order.  This
                         * will help prevent the same colonies from getting priority when
                         * the global stockpiles are low.
                         */
                        Algorithms.RandomShuffleInPlace(colonies);

                        /* Iterate through each colony */
                        foreach (var colony in colonies)
                        {
                            var projectFinished = false;
                            var shipyard = colony.Shipyard;

                            /* We want to capture the resources produced locally at the colony. */
                            var resources = new ResourceValueCollection();
                            resources[ResourceType.Dilithium] = colony.NetDilithium;
                            resources[ResourceType.Deuterium] = colony.NetDeuterium;
                            resources[ResourceType.RawMaterials] = colony.NetRawMaterials;
                            //resources[ResourceType.Personnel] = colony.NetPersonnel;

                            /* 
                             * Go ahead and add distribute locally recruited personnel at the empire
                             * level.  If we try to contribute them to the local build projects
                             * directly, we risk screwing up the Officer/Int. Affairs/Ext. Affairs
                             * allocations.
                             */
/*
                            if (resources[ResourceType.Personnel] > 0)
                            {
                                civManager.Personnel.AddPersonnel(resources[ResourceType.Personnel]);
                                resources[ResourceType.Personnel] = 0;
                            }
*/

                            /* We only care about colonies with (active) shipyards */
                            if (shipyard == null)
                                goto ProcessResources;

                            var queue = shipyard.BuildQueue;
                            
                            BuildProject lastProject = null;

                            for (var i = 0; i < shipyard.BuildSlots.Length; i++)
                            {
                                /* We want to capture the industry output available for this build slot. */
                                var slot = shipyard.BuildSlots[i];

                                if (!slot.IsActive || slot.OnHold)
                                    continue;
                                
                                var output = shipyard.ShipyardDesign.BuildSlotOutput;

                                if (slot.HasProject && slot.Project.IsCompleted)
                                {
                                    slot.Project.Finish();
                                    slot.Project = null;

                                    projectFinished = true;
                                }
                                
                                if (slot.Project == null)
                                {
                                    if (queue.Count > 0)
                                        shipyard.ProcessQueue();
                                }

                            BuildNextProject:

                                if (slot.HasProject && output > 0)
                                {
                                    lastProject = slot.Project;

                                    /*
                                     * We're going to build a new resource collection that will
                                     * allow the production project to draw from both the local
                                     * resources and the global stockpiles.  Once the project has
                                     * advanced, we can compare the amounts remaining in the new
                                     * collection to those in our local collection to see how many
                                     * resources were taken from the global stockpile (if any)
                                     * and then deduct the approprite amounts from the stockpiles.
                                     */
                                    var totalResources = new ResourceValueCollection
                                                         {
                                                             {
                                                                 ResourceType.Dilithium,
                                                                 resources[ResourceType.Dilithium] + civManager.Resources.Dilithium.CurrentValue
                                                             },
                                                             {
                                                                 ResourceType.Deuterium,
                                                                 resources[ResourceType.Deuterium] + civManager.Resources.Deuterium.CurrentValue
                                                             },
                                                             {
                                                                 ResourceType.RawMaterials,
                                                                 resources[ResourceType.RawMaterials] + civManager.Resources.RawMaterials.CurrentValue
                                                             }
                                                         };

                                    var totalResourcesCopy = totalResources.Clone();

                                    /* 
                                     * Advance the production project.  This will automatically
                                     * deduct the necessary (optimal) amounts from the 'industry'
                                     * and 'totalRes' parameters.
                                     */
                                    slot.Project.Advance(ref output, totalResources);

                                    /* 
                                     * If the project was finished, call Finish() to produce the
                                     * results, then deqeue next project.
                                     */
                                    if (slot.Project.IsCompleted)
                                    {
                                        slot.Project.Finish();
                                        slot.Project = null;
                                        projectFinished = true;
                                        if (queue.Count > 0)
                                            shipyard.ProcessQueue();
                                    }

                                    /* Check to see if we tapped into the global resource stockpiles */
                                    var dDilithium = totalResourcesCopy[ResourceType.Dilithium] -
                                                     totalResources[ResourceType.Dilithium];
                                    var dDeuterium = totalResourcesCopy[ResourceType.Deuterium] -
                                                     totalResources[ResourceType.Deuterium];
                                    var dRawMaterials = totalResourcesCopy[ResourceType.RawMaterials] -
                                                        totalResources[ResourceType.RawMaterials];
                                    //var dPersonnel = totalResourcesCopy[ResourceType.Personnel] -
                                    //                 totalResources[ResourceType.Personnel];

                                    if (dDilithium > resources[ResourceType.Dilithium])
                                    {
                                        civManager.Resources.Dilithium.AdjustCurrent(
                                            resources[ResourceType.Dilithium] - dDilithium);
                                        resources[ResourceType.Dilithium] = 0;
                                    }
                                    else
                                    {
                                        resources[ResourceType.Dilithium] -= dDilithium;
                                    }

                                    if (dDeuterium > resources[ResourceType.Deuterium])
                                    {
                                        civManager.Resources.Deuterium.AdjustCurrent(
                                            resources[ResourceType.Deuterium] - dDeuterium);
                                        resources[ResourceType.Deuterium] = 0;
                                    }
                                    else
                                    {
                                        resources[ResourceType.Deuterium] -= dDeuterium;
                                    }

                                    if (dRawMaterials > resources[ResourceType.RawMaterials])
                                    {
                                        civManager.Resources.RawMaterials.AdjustCurrent(
                                            resources[ResourceType.RawMaterials] - dRawMaterials);
                                        resources[ResourceType.RawMaterials] = 0;
                                    }
                                    else
                                    {
                                        resources[ResourceType.RawMaterials] -= dRawMaterials;
                                    }

/*
                                    if (dPersonnel > 0)
                                    {
                                        civManager.Personnel[PersonnelCategory.Officers].AdjustCurrent(-dPersonnel);
                                    }
*/
                                }

                                /* 
                                 * If we haven't expended all of our available industry, run
                                 * through the production cycle again.  This should only happen
                                 * if the current build project was just completed during the
                                 * last pass.
                                 */
                                if ((slot.Project != null) && (slot.Project != lastProject) && (output > 0))
                                    goto BuildNextProject;

                                if (projectFinished && 
                                    (slot.Project == null) && 
                                    !shipyard.BuildSlots.Any(o => o.Project != null))
                                {
                                    civManager.SitRepEntries.Add(new BuildQueueEmptySitRepEntry(civ, colony, true));
                                }
                            }

                        ProcessResources:
                            /* 
                             * Take any resources that we did not use locally for
                             * production and give them to the global stockpile for
                             * the current civilization.
                             */
                            if (resources[ResourceType.Dilithium] > 0)
                            {
                                civManager.Resources.Dilithium.AdjustCurrent(
                                    resources[ResourceType.Dilithium]);
                            }
                            if (resources[ResourceType.Deuterium] > 0)
                            {
                                civManager.Resources.Deuterium.AdjustCurrent(
                                    resources[ResourceType.Deuterium]);
                            }
                            if (resources[ResourceType.RawMaterials] > 0)
                            {
                                civManager.Resources.RawMaterials.AdjustCurrent(
                                    resources[ResourceType.RawMaterials]);
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        errors.Push(e);
                    }
                    finally
                    {
                        GameContext.PopThreadContext();
                    }
                });

            if (!errors.IsEmpty)
                throw new AggregateException(errors);
        }
        #endregion

        #region DoMorale() Method
        void DoMorale(GameContext game)
        {
            var errors = new System.Collections.Concurrent.ConcurrentStack<Exception>();
            /*
             * We want to update the morale level of each star system.  For each civilization,
             * we use two modifiers: a baseline morale level, and the rate at which morale
             * drifts towards that baseline when no other morale changes occur during a turn.
             */
            //Table baseMoraleTable = GameContext.Current.Tables.MoraleTables["BaseMoraleLevels"];
            //Table driftRateTable = GameContext.Current.Tables.MoraleTables["MoraleDriftRate"];
            ParallelForEach(
                GameContext.Current.Civilizations,
                civ =>
                {
                    GameContext.PushThreadContext(game);
                    try
                    {
                        int globalMorale = 0;
                        CivilizationManager civManager = GameContext.Current.CivilizationManagers[civ.CivID];

                        /* Calculate any empire-wide morale bonuses */
                        foreach (Bonus bonus in civManager.GlobalBonuses)
                        {
                            if (bonus.BonusType == BonusType.MoraleEmpireWide)
                            {
                                globalMorale += bonus.Amount;
                            }
                        }

                        /* Iterate through each colony. */
                        foreach (Colony colony in civManager.Colonies)
                        {
                            /* Add the empire-wide morale adjustments. */
                            colony.Morale.AdjustCurrent(globalMorale);

                            /* Add any morale bonuses from active buildings at the colony. */
                            foreach (Building building in colony.Buildings)
                            {
                                if (!building.IsActive)
                                    continue;
                                foreach (Bonus buildingBonus in building.BuildingDesign.Bonuses)
                                {
                                    if (buildingBonus.BonusType == BonusType.Morale)
                                    {
                                        colony.Morale.AdjustCurrent(buildingBonus.Amount);
                                    }
                                }
                            }

                            /*
                             * If morale has not changed in this colony for any reason, then we will
                             * cause the morale level to drift towards the founding civilization's
                             * base morale level.
                             */
                            //if (colony.Morale.CurrentChange == 0)
                            //{
                            //    int drift;
                            //    int baseMorale;
                            //    string ownerId = colony.OriginalOwner.Key;
                            //    if (baseMoraleTable[ownerId] != null)
                            //    {
                            //        baseMorale = Number.ParseInt32(baseMoraleTable[ownerId][0]);
                            //    }
                            //    else
                            //    {
                            //        baseMorale = Number.ParseInt32(baseMoraleTable[0][0]);
                            //    }
                            //    if (driftRateTable[ownerId] != null)
                            //    {
                            //        drift = Number.ParseInt32(driftRateTable[ownerId][0]);
                            //    }
                            //    else
                            //    {
                            //        drift = Number.ParseInt32(driftRateTable[0][0]);
                            //    }
                            //    if (baseMorale > colony.Morale.CurrentValue)
                            //    {
                            //        drift = Math.Min(drift, baseMorale - colony.Morale.CurrentValue);
                            //        colony.Morale.AdjustCurrent(drift);
                            //    }
                            //    else if (colony.Morale.CurrentValue > baseMorale)
                            //    {
                            //        drift = Math.Max(-drift, baseMorale - colony.Morale.CurrentValue);
                            //        colony.Morale.AdjustCurrent(drift);
                            //    }
                            //}
                            colony.Morale.UpdateAndReset();
                        }
                    }
                    catch (Exception e)
                    {
                        errors.Push(e);
                    }
                    finally
                    {
                        GameContext.PopThreadContext();
                    }
                });

            if (!errors.IsEmpty)
                throw new AggregateException(errors);
        }
        #endregion

        #region DoTrade() Method
        void DoTrade(GameContext game)
        {
            Table popReqTable = GameContext.Current.Tables.ResourceTables["TradeRoutePopReq"];
            Table popModTable = GameContext.Current.Tables.ResourceTables["TradeRoutePopMultipliers"];

            float sourceMod = Number.ParseSingle(popModTable["Source"][0]);
            float targetMod = Number.ParseSingle(popModTable["Target"][0]);

            var errors = new System.Collections.Concurrent.ConcurrentStack<Exception>();

            ParallelForEach(
                GameContext.Current.Civilizations,
                civ =>
                {
                    GameContext.PushThreadContext(game);
                    try
                    {
                        int popForTradeRoute;
                        var civManager = GameContext.Current.CivilizationManagers[civ.CivID];

                        /*
                         * See what the minimum population level is for a new trade route for the
                         * current civilization.  If one is not specified, use the default.
                         */
                        if (popReqTable[civManager.Civilization.Key] != null)
                            popForTradeRoute = Number.ParseInt32(popReqTable[civManager.Civilization.Key][0]);
                        else
                            popForTradeRoute = Number.ParseInt32(popReqTable[0][0]);

                        var colonies = GameContext.Current.Universe.FindOwned<Colony>(civ);

                        /* Iterate through each colony... */
                        foreach (var colony in colonies)
                        {
                            /*
                             * For each established trade route, ensure that the target colony is
                             * a valid choice.  If it isn't, break it.  Otherwise, calculate the
                             * revised credit total.
                             */
                            foreach (var route in colony.TradeRoutes)
                            {
                                if (!route.IsValidTargetColony(route.TargetColony))
                                {
                                    route.TargetColony = null;
                                }
                                if (route.TargetColony != null)
                                {
                                    route.Credits = (int)((sourceMod * route.SourceColony.Population.CurrentValue) +
                                                          (targetMod * route.TargetColony.Population.CurrentValue));
                                }
                            }
                            
                            /*
                             * Calculate how many trade routes the colony is allowed to have.
                             * Take into consideration any routes added by building bonuses.
                             */
                            int tradeRoutes = colony.Population.CurrentValue / popForTradeRoute;
                            
                            tradeRoutes += colony.Buildings
                                .Where(o => o.IsActive)
                                .SelectMany(o => o.BuildingDesign.Bonuses)
                                .Where(o => o.BonusType == BonusType.TradeRoutes)
                                .Sum(o => o.Amount);

                            /*
                             * If the colony doesn't have as many trade routes as it should, then
                             * we need to add some more.
                             */
                            if (tradeRoutes > colony.TradeRoutes.Count)
                            {
                                int tradeRouteDeficit = tradeRoutes - colony.TradeRoutes.Count;
                                for (int i = 0; i < tradeRouteDeficit; i++)
                                    colony.TradeRoutes.Add(new TradeRoute(colony));
                            }
                            
                            /*
                             * If the colony has too many trade routes, we need to remove some.
                             * To be generous, we sort them in order of credits generated so that
                             * we remove the least valuable routes.
                             */
                            else if (tradeRoutes < colony.TradeRoutes.Count)
                            {
                                var extraTradeRoutes = colony.TradeRoutes
                                    .OrderByDescending(o => o.Credits)
                                    .SkipWhile((o, i) => i < tradeRoutes)
                                    .ToArray();
                                foreach (var extraTradeRoute in extraTradeRoutes)
                                    colony.TradeRoutes.Remove(extraTradeRoute);
                            }
                            
                            /*
                             * Iterate through the remaining trade routes and deposit the credit
                             * income into the civilization's treasury.
                             */
                            foreach (var route in colony.TradeRoutes)
                            {
                                colony.CreditsFromTrade.AdjustCurrent(route.Credits);
                            }
                            
                            /*
                             * Apply all "+% Trade Income" and "+% Credits" bonuses at this colony.
                             */
                            var tradeBonuses = (int)colony.ActiveBuildings
                                .SelectMany(o => o.BuildingDesign.Bonuses)
                                .Where(o => ((o.BonusType == BonusType.PercentTradeIncome) || (o.BonusType == BonusType.PercentCredits)))
                                .Sum(o => 0.01f * o.Amount);
                            colony.CreditsFromTrade.AdjustCurrent(tradeBonuses);
                            civManager.Credits.AdjustCurrent(colony.CreditsFromTrade.CurrentValue);
                            colony.ResetCreditsFromTrade();
                        }
                        
                        /* 
                         * Apply all global "+% Total Credits" bonuses for the civilization.  At present, we have now
                         * completed all adjustments to the civilization's treasury for this turn.  If that changes in
                         * the future, we may need to move this operation.
                         */
                        var globalBonusAdjustment = (int)(0.01f * civManager.GlobalBonuses
                            .Where(o => o.BonusType == BonusType.PercentTotalCredits)
                            .Sum(o => o.Amount));
                        civManager.Credits.AdjustCurrent(globalBonusAdjustment);
                    }
                    catch (Exception e)
                    {
                        errors.Push(e);
                    }
                    finally
                    {
                        GameContext.PopThreadContext();
                    }
                });

            if (!errors.IsEmpty)
                throw new AggregateException(errors);
        }
        #endregion

        #region DoPostTurnOperations() Method
        void DoPostTurnOperations(GameContext game)
        {
            var destroyedOrbitals = GameContext.Current.Universe.Find<Orbital>(o => o.HullStrength.IsMinimized);
            var allFleets = GameContext.Current.Universe.Find<Fleet>(UniverseObjectType.Fleet);

            foreach (Orbital orbital in destroyedOrbitals)
            {
                GameContext.Current.Universe.Destroy(orbital);
                GameContext.Current.CivilizationManagers[orbital.OwnerID].SitRepEntries.Add(
                    new OrbitalDestroyedSitRepEntry(orbital));
            }

            foreach (Fleet fleet in allFleets)
            {
                if (fleet.Ships.Count == 0)
                {
                    if (fleet.Order != null)
                    {
                        fleet.Order.OnOrderCancelled();
                    }
                    GameContext.Current.Universe.Destroy(fleet);
                }
                else if (fleet.Order != null)
                {
                    fleet.Order.OnTurnEnding();
                }
            }

            foreach (CivilizationManager civManager in GameContext.Current.CivilizationManagers)
            {
                /*
                 * Reset the resource stockpile meters now that we have finished
                 * production for the each civilization.  This will update the
                 * last base value of the meters so that the net change this turn
                 * is properly reflected.  Do the same for the personnel pool
                 * and the credit treasury.
                 */
                civManager.Resources.UpdateAndReset();
                civManager.Personnel.UpdateAndReset();
                civManager.Credits.UpdateAndReset();
                civManager.OnTurnFinished();
            }            

            GameContext.Current.TurnNumber++;
        }
        #endregion

        public void DoAIPlayers(object gameContext)
        {
            var errors = new System.Collections.Concurrent.ConcurrentStack<Exception>();
            var game = gameContext as GameContext;
            if (game == null)
                throw new ArgumentException("gameContext must be a valid GameContext instance");

            ParallelForEach(
                GameContext.Current.Civilizations,
                civ =>
                {
                    GameContext.PushThreadContext(game);
                    try
                    {
                        if (!civ.IsHuman)
                        {
                            CivilizationManager civManager = GameContext.Current.CivilizationManagers[civ];
                            civManager.DesiredBorders = PlayerAI.CreateDesiredBorders(civ);
                        }
                    }
                    catch (Exception e)
                    {
                        errors.Push(e);
                    }
                    finally
                    {
                        GameContext.PopThreadContext();
                    }
                });

            if (!errors.IsEmpty)
                throw new AggregateException(errors);
        }

        /// <summary>
        /// Raises the <see cref="CombatOccurring"/> event.
        /// </summary>
        /// <param name="combat">The combat assets.</param>
        private void OnCombatOccurring(IList<CombatAssets> combat)
        {
            if (combat == null)
                throw new ArgumentNullException("combat");
            if (CombatOccurring != null)
                CombatOccurring(combat);
        }

        /// <summary>
        /// Resets the combat wait handle.
        /// </summary>
        public void NotifyCombatFinished()
        {
            CombatReset.Set();
        }

        private static ParallelLoopResult ParallelForEach<TSource>(
            [NotNull] IEnumerable<TSource> source,
            [NotNull] Action<TSource> body)
        {
            return Parallel.ForEach(
                source,
                new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount * 4 },
                body);
        }
    }
}
